package VMM;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.text.DecimalFormat;

import Exceptions.FrameOverflowEx;
import Exceptions.OffsetEx;
import Exceptions.PageOverflowEx;

/**
 * Class which models Virtual Memory Manager
 * 
 * @author allanwatson
 * @changelog 9/2/12 Tidying up the code, no longer responsible for creating the
 *            page table, tlb or memory. These are parameterised. Dynamic
 *            allocation of storage capacities and sizes.
 * 
 *            16/2/12 Included count in the print out. 18/2/12 Modified code to
 *            interact with updated TLB and page tables. Error method for
 *            printing errors. Code tidy up and comment fixing. Added new
 *            exception handling 19/2/12 Removed parsing to a separate class.
 *            Separated code from addressTranslate to new methods
 * 
 */
public class VMM {
	private PageTable pT;
	private PhysicalMem pM;
	private RandomAccessFile backingStore;
	private ITLB tlb;
	private int pageFaults, tlbHit, count, pageHits = 0;
	private int logAddr, pageNo, offset, frameNo, value, physAddr;
	private String status;

	public VMM(PageTable pageTab, PhysicalMem physMem, ITLB t)
			throws FileNotFoundException {
		pT = pageTab;
		pM = physMem;
		tlb = t;

		File fileName = new File("BACKING_STORE");
		backingStore = new RandomAccessFile(fileName, "r");

	}

	/**
	 * Print the Page fault, Page hit and TLB hit rates
	 */
	public void printRates() {
		DecimalFormat f = new DecimalFormat("#.##");
		Double pFR = (double) pageFaults / count * 100;
		Double pHR = (double) pageHits / count * 100;
		Double THR = (double) tlbHit / count * 100;

		System.out.println("*******************");
		System.out.println("Page Faults: " + pageFaults + " (" + f.format(pFR)
				+ "%)");
		System.out.println("Page Hits: " + pageHits + " (" + f.format(pHR)
				+ "%)");
		System.out.println("TLB Hits: " + tlbHit + " (" + f.format(THR) + "%)");
	}

	/**
	 * Print the details of the address being loaded
	 */
	public void printLineDetails() {
		System.out.print(count + "  ");
		System.out.print("	Virtual: " + logAddr);
		System.out.print("	Page No: " + pageNo);
		System.out.print("	Offset: " + offset);
		System.out.print("	" + status + " ");
		System.out.print("	FrameNo: " + frameNo);
		System.out.print("	Physical: " + physAddr);
		System.out.println("	Value: " + value);
	}

	/**
	 * Helper method for testing
	 */
	public String printLineDetailsTesting() {
		return ("Virtual address: " + logAddr + " Physical address: "
				+ physAddr + " Value: " + value);
	}

	/**
	 * Extract parts of address and lookup TLB, Page Table before bringing in
	 * from backing storage to physical memory
	 */
	public void addressTranslate(int a) throws IOException, FrameOverflowEx,
			PageOverflowEx, OffsetEx {

		logAddr = a & 0x0000FFFF; // extract parts from the address
		offset = (a & 0x00FF);
		pageNo = (a & 0xFF00) >> 8;

		if (tlb.containsPage(pageNo))
			frameNo = checkTLB();

		else if (!pT.pageIsEmpty(pageNo))
			frameNo = checkPageTable();

		// bring in from backing storage if frameNo invalid or Page table empty
		else if (pT.pageIsEmpty(pageNo) | frameNo == -1) {
			retrieveBackingStorage();
		}

		value = getValue();
		physAddr = frameNo * pM.getFrameSize() + offset;
		count++;
	}

	/**
	 * Returns value from memory
	 */
	public int getValue() throws FrameOverflowEx, PageOverflowEx, OffsetEx {
		return pM.get(frameNo, offset);
	}

	private int retrieveBackingStorage() throws IOException, FrameOverflowEx {

		byte[] data = new byte[pM.getFrameSize()];
		status = "Page Fault";
		backingStore.seek(pageNo * pM.getFrameSize()); // read the pageNo *
														// pT
		backingStore.read(data);

		frameNo = pM.addPage(data); // add to memory receive a frame number
		pT.add(pageNo, frameNo);// add to page table

		tlb.put(pageNo, frameNo);// add to TLB
		return pageFaults++;
	}

	/**
	 * Retrieve pageNumber from Page Table
	 */
	private int checkPageTable() {
		status = "Page Hit";
		if (frameNo != -1)
			pageHits++;
		return pT.getFrameNo(pageNo);
	}

	/**
	 * Retrieve pageNumber from TLB
	 */
	private int checkTLB() {
		status = "TLB Hit";
		if (frameNo != -1)
			tlbHit++;
		return frameNo = (Integer) tlb.getFrameNo(pageNo);
	}

}
